/**
 * 
 */
package IC.asm;

import java.util.Date;
import java.util.List;
import java.util.Map;

import util.LinkedList;

import IC.AST.PropogatingVisitor;
import IC.AST.nodes.*; 
import IC.AST.DefaultVisitor;
import IC.Analyzer.SymTabEntry;
import IC.asm.Instruction.Ops;
import IC.asm.decl.*;
import IC.asm.decl.SectionDecl.Sections;
import IC.asm.operands.*;
import IC.asm.operands.MachineReg.Regs;

/**
 *	TODO iterate on classes
 *	1. write down labels
 *  2. write literals
 *	3. write DV for each class
 *	4. add code of declared functions, from previous stage, but not for `main'
 *	5. add `main' code
 *
 */
public class ASMGenerator extends DefaultVisitor<Object, Object> 
implements PropogatingVisitor<Object, Object> {
	private		List<ASMEntry> 	asm;
	private		Program			root;
	
	/**
	 * constructs a ASM Generator
	 * @param root
	 */
	private ASMGenerator(Program root) {
		asm = new LinkedList<ASMEntry>();
		this.root = root;
	}
	
	/**
	 * Generates code for a lowered AST tree
	 * @param prog
	 */
	public static List<ASMEntry> Process(Program prog) 
	throws Exception {
		ASMGenerator g = new ASMGenerator(prog);
		prog.accept(g,null);
		return g.asm;
	}

	/**
	 * 1. print string literals
	 * 2. pront dispatch vectors
	 * 3. print code
	 */
	public Object visit(Program that, Object context)
	throws Exception {
		
		// file title
		asm.add(new ASMDecl("title",  "\""+ that.getFileName() +"\""));
		asm.add(new CommentEntry(""));
		asm.add(new CommentEntry("auto-generated "+new Date()));
		asm.add(new CommentEntry(""));

		// intel syntax
		//asm.add(new ASMDecl("intel_syntax", "noprefix"));
		
		// data section
		asm.add(new ASMDecl("global", "__ic_main"));
		asm.add(new CommentEntry("*** data section ***"));
		asm.add(new SectionDecl(Sections.data));
		asm.add(new ASMDecl("align", "4"));
		
		// string literals
		Map<String,String> stringLiterals = that.getStringLiterals();
		
		// add error messages
		stringLiterals.put("Null pointer exception", "strNPE");
		stringLiterals.put("Array bounds exception", "strABE");
		stringLiterals.put("Array allocation exception", "strAAE");
		stringLiterals.put("Division by zero exception", "strDBE");
		
		for(String s : stringLiterals.keySet()) {
			String val = stringLiterals.get(s);
			
			asm.add(new IntDecl(s.length()));
			asm.add(new LabelDecl(val));
			asm.add(new StringDecl(s));
		}
		
		// dispatch vectors
		for (ClassDecl cls: that.getClasses()) {
			if (!cls.getClassID().equals("Library")) {
				LabelDecl lbl = 
					new LabelDecl(cls.getClassInfo().getDVName());
				asm.add(lbl);
				for(IC.lir.operands.Label label : cls.getClassInfo().getDVDecl().getLabels()) {
					asm.add(new IntDecl(label.getName()));
				}
			}
		}

		// code section
		asm.add(new CommentEntry("*** code section ***"));
		asm.add(new SectionDecl(Sections.text));
		asm.add(new ASMDecl("align", "4"));

		for (ClassDecl cls: that.getClasses()) {
			if (!cls.getClassID().equals("Library")) {
				cls.accept(this,context);
			}
		}
		
		// exceptions
		asm.addAll(getExceptionCode(NullPointerStr, NullPointerProc));
		asm.addAll(getExceptionCode(IndexOutStr, IndexOutProc));
		asm.addAll(getExceptionCode(ArrayAllocStr, ArrayAllocProc));
		asm.addAll(getExceptionCode(DivByZeroStr, DivByZeroProc));
		
		return null;
	}
	
	
	/**
	 * visit a method
	 * add the method code
	 */
	public Object visit(MethodDecl that, Object context) throws Exception {

		// resolve method
		SymTabEntry STEntry = that.getSymTab().resolve(that.getMethodName());
		int numLocalVars = that.getNumLocals();

		// method entry point
		asm.add(new LabelDecl(that.getLabel()));
		
		// prologue
		asm.add(new Instruction(Ops.PUSH, Regs.ebp));
		asm.add(new Instruction(Ops.MOV, Regs.esp, Regs.ebp));

		// push calee-save
		asm.add(new Instruction(Ops.PUSH, Regs.ebx));
		asm.add(new Instruction(Ops.PUSH, Regs.esi));
		asm.add(new Instruction(Ops.PUSH, Regs.edi));
		
		// allocate room for local variables
		if(numLocalVars > 0) {
			asm.add(new Instruction(Ops.SUB, new IntImmediate(numLocalVars*4), Regs.esp));
		}

		asm.addAll(that.getAsmCode());
		
		// free room from local variables
		if(numLocalVars > 0) {
			asm.add(new Instruction(Ops.ADD, new IntImmediate(numLocalVars*4), Regs.esp));
		}

		// pop calee-save
		asm.add(new Instruction(Ops.POP, Regs.edi));
		asm.add(new Instruction(Ops.POP, Regs.esi));
		asm.add(new Instruction(Ops.POP, Regs.ebx));
		
		// epilogue
		asm.add(new Instruction(Ops.POP, Regs.ebp));
		asm.add(new Instruction(Ops.RET));

		return null;
	}
	
	// utils
	
	public LabelDecl NullPointerStr = new LabelDecl("strNPE");
	public LabelDecl NullPointerProc = new LabelDecl("labelNPE");
	
	public LabelDecl IndexOutStr = new LabelDecl("strABE");
	public LabelDecl IndexOutProc = new LabelDecl("labelABE");
	
	public LabelDecl ArrayAllocStr = new LabelDecl("strAAE");
	public LabelDecl ArrayAllocProc = new LabelDecl("labelAAE");
	
	public LabelDecl DivByZeroStr = new LabelDecl("strDBE");
	public LabelDecl DivByZeroProc = new LabelDecl("labelDBE");
	
	
	public List<ASMEntry> getExceptionCode(LabelDecl strLbl, LabelDecl procLbl) {
		List<ASMEntry> asm = new LinkedList<ASMEntry>();
		
		// TODO add align
		asm.add(procLbl);
		asm.add(new Instruction(Ops.PUSH, strLbl.getLabel()));
		asm.add(new Instruction(Ops.CALL, new Label("__println")));
		asm.add(new Instruction(Ops.PUSH, new IntImmediate(1)));
		asm.add(new Instruction(Ops.CALL, new Label("__exit")));
		
		return asm;
	}
	
}